#include "DctCompression.h"

int	zigzag[8][8] = {
    {0, 1, 5, 6,14,15,27,28},
    {2, 4, 7,13,16,26,29,42},
    {3, 8,12,17,25,30,41,43},
    {9,11,18,24,31,40,44,53},
    {10,19,23,32,39,45,52,54},
    {20,22,33,38,46,51,55,60},
    {21,34,37,47,50,56,59,61},
    {35,36,48,49,57,58,62,63},
};

DctCompression::DctCompression(FILE *file, int width, int height, int type)
{
    this->matrix = MatrixQuantification(Quality);

    Header header;
    header.height = height;
    header.width  = width;
    header.type = type;
    this->rle = new RleManager(file, header);
    if (height == 0)
        this->rle->ReadHeader();
    else
        this->rle->WriteHeader(header);
}

DctCompression::~DctCompression()
{
    delete this->rle;
    delete this->matrix;
}

//Creat Matrix for Quantification Q = quality
int *DctCompression::MatrixQuantification(int Q)
{
    int *matrix = new int[64];

    for (int i = 0; i < 8; ++i)
        for (int j = 0; j < 8; ++j)
            matrix[(i*8)+j] =  (1 + Q * (1 + i + j));
    return matrix;
}

//Apply quantification with Matrix quantification
int *DctCompression::DctQuantification(int *dct, int *matrixQ)
{

    int *dctQ = new int[64];
    for (int i = 0; i < 64 ; ++i)
    {
        //float res = ((float)dct[i] / matrixQ[i]) + 0.5f;
        dctQ[i] = dct[i];//(int)res;
    }
    return dctQ;
}

//Apply quantification with Matrix dequantification
int *DctCompression::DctDequantification(int *dctQ, int *matrixQ)
{
    return dctQ;
    int *dct = new int[64];

    for (int i = 0; i < 64 ; ++i)
        dct[i] = /*(int)((*/dctQ[i]/* * matrixQ[i]))*/;
    return dct;
}

void DisplayTab(int *tab)
{
    std::cout << std::endl;
    for (int i = 0; i < 64; ++i)
    {
        if (i % 8 == 0)
            std::cout << std::endl;
        std::cout << tab[i] << " ";
    }
    std::cout << std::endl;
}

DCT_ENCODED *DctCompression::DCTEncode(cv::Mat &yuv411)
{
    //Coeff
    int coeffBlue[64];
    int coeffRed[64];
    int coeffLum[64];

    //Block
    int blockRed[64];
    int blockBlue[64];
    int blockLuminance[64];

    //Allocation
    DCT_ENCODED *dctData = new DCT_ENCODED(yuv411.size().height, yuv411.size().width);

    //DCT for chrominance
    int halfWidth = yuv411.size().width / 2;
    int halfHeight = yuv411.size().height / 2;

    for (int i = 0; i < halfHeight; i += 8)
    {
        for (int j = 0; j < halfWidth; j += 8)
        {
            //Get 8*8 block chrominances (Red & Blue)
            for (int z = 0; z < 64; ++z)
            {
                cv::Vec3b pixel = yuv411.at<cv::Vec3b>(i + (z / 8), j + (z % 8));

                blockRed[z] = pixel.val[2]; //Creat block chrominance RED
                blockBlue[z] = pixel.val[0];//Creat block chrominance BLUE
            }
            //DCT + quantification & linearisation
            this->Dct(blockBlue, coeffBlue);
            this->Dct(blockRed, coeffRed);

            //Write block

            this->rle->WriteBlockCrominance(coeffBlue);
            this->rle->WriteBlockCrominance(coeffRed);
        }
    }

    //DCT for Luminance
    int count = 0;
    for (int i = 0; i < yuv411.size().height ; i += 8)
    {
        for (int j = 0; j < yuv411.size().width; j += 8)
        {
            for (int z = 0; z < 64; ++z)
            {
                cv::Vec3b pixel = yuv411.at<cv::Vec3b>(i + (z / 8), j + (z % 8));
                blockLuminance[z] = pixel.val[1]; //Creat block Luminance
            }
            //DCT + quantification & linearisation
            this->Dct(blockLuminance, coeffLum);

            //Write Block
            this->rle->WriteBlockLuminance(coeffLum);
            count += 64;
        }
    }
    return dctData;
}

cv::Mat DctCompression::DCTUnencode()
{
    Header &head = this->rle->GetHeader();

    DCT_ENCODED *DctTable = this->rle->RleDecode();
    cv::Mat img(head.height, head.width, head.type);
    //Coeff
    int coeffBlue[64];
    int coeffRed[64];
    int coeffLum[64];

    //Block
    int blockRed[64];
    int blockBlue[64];
    int blockLuminance[64];
    int h = 0;
    int w = 0;
    //Chrominance dequantification
    for (int i = 0; i < head.height / 2; i += 8)
    {
        for (int j = 0; j < head.width / 2; j += 8)
        {
            //Get 8*8 block chrominances (Red & Blue)
            for (int z = 0; z < 64; ++z)
            {
                cv::Vec2i pixelChro = DctTable->tabChrominance[i + (z / 8)][j + (z % 8)];
                coeffRed[z] = pixelChro.val[1]; //Get coeff chrominance RED
                coeffBlue[z] = pixelChro.val[0];//Get coeff chrominance BLUE
            }
            //IDCT + Quantification & linearisation
            this->IDct(coeffBlue, blockBlue);
            this->IDct(coeffRed, blockRed);

            //Save coeff red & blue
            for (int z = 0; z < 64; ++z)
            {
                h = i + (z / 8);
                w =  j + (z % 8);
                cv::Vec3b pixel = img.at<cv::Vec3b>(h, w);
                pixel.val[2] = blockRed[z];
                pixel.val[0] = blockBlue[z];

                img.at<cv::Vec3b>(h, w) = pixel;
            }
        }
    }

    int count = 0;
    //DCT for Luminance
    for (int i = 0; i < head.height ; i += 8)
    {
        for (int j = 0; j < head.width; j += 8)
        {
            for (int z = 0; z < 64; ++z)
            {
                int lum = DctTable->luminance[count + z];
                coeffLum[z] = lum;	 //Creat coeff Luminance
            }
            count += 64;
            //IDCT + quantification & linearisation
            this->IDct(coeffLum, blockLuminance);
            //Save coeff Luminance
            for (int z = 0; z < 64; ++z)
            {
                cv::Vec3b &pixel = img.at<cv::Vec3b>(i + (z / 8), j + (z % 8));
                pixel.val[1] = blockLuminance[z];
            }
        }
    }
    //Delete DctTable
    return img;
}

int DctCompression::Dct(int *block, int *coeff)
{
    int j;
    int j1;
    int i;
    int k;
    float	b[8];
    float 	b1[8];
    float 	d[8][8];
    double 	f0=.7071068;
    double   f1=.4903926;
    double   f2=.4619398;
    double   f3=.4157348;
    double   f4=.3535534;
    double 	f5=.2777851;
    double   f6=.1913417;
    double   f7=.0975452;
    int DQ[64];

    for (i = 0, k = 0; i < 8; i++, k += 8)
    {
        //Write row in buffer b get 8values
        for (j = 0; j < 8; j++)
        {
            b[j] = block[k+j];
        }

        for (j = 0; j < 4; j++)
        {
            j1 = 7 - j;
            b1[j] = b[j] + b[j1];
            b1[j1] = b[j] - b[j1];
        }
        b[0] = b1[0] + b1[3];
        b[1] = b1[1] + b1[2];
        b[2] = b1[1] - b1[2];
        b[3] = b1[0] - b1[3];
        b[4] = b1[4];
        b[5] = (b1[6] - b1[5]) * f0;
        b[6] = (b1[6] + b1[5]) * f0;
        b[7] = b1[7];
        d[i][0] = (b[0] + b[1]) * f4;
        d[i][4] = (b[0] - b[1]) * f4;
        d[i][2] = b[2] * f6 + b[3] * f2;
        d[i][6] = b[3] * f6 - b[2] * f2;
        b1[4] = b[4] + b[5];
        b1[7] = b[7] + b[6];
        b1[5] = b[4] - b[5];
        b1[6] = b[7] - b[6];
        d[i][1] = b1[4] * f7 + b1[7] * f1;
        d[i][5] = b1[5] * f3 + b1[6] * f5;
        d[i][7] = b1[7] * f7 - b1[4] * f1;
        d[i][3] = b1[6] * f3 - b1[5] * f5;
    }

    for (i = 0; i < 8; i++)
    {
        for (j = 0; j < 4; j++)
        {
            j1 = 7 - j;
            b1[j] = d[j][i] + d[j1][i];
            b1[j1] = d[j][i] - d[j1][i];
        }
        b[0] = b1[0] + b1[3];
        b[1] = b1[1] + b1[2];
        b[2] = b1[1] - b1[2];
        b[3] = b1[0] - b1[3];
        b[4] = b1[4];
        b[5] = (b1[6] - b1[5]) * f0;
        b[6] = (b1[6] + b1[5]) * f0;
        b[7] = b1[7];
        d[0][i] = (b[0] + b[1]) * f4;
        d[4][i] = (b[0] - b[1]) * f4;
        d[2][i] = b[2] * f6 + b[3] * f2;
        d[6][i] = b[3] * f6 - b[2] * f2;
        b1[4] = b[4] + b[5];
        b1[7] = b[7] + b[6];
        b1[5] = b[4] - b[5];
        b1[6] = b[7] - b[6];
        d[1][i] = b1[4] * f7 + b1[7] * f1;
        d[5][i] = b1[5] * f3 + b1[6] * f5;
        d[7][i] = b1[7] * f7 - b1[4] * f1;
        d[3][i] = b1[6] * f3 - b1[5] * f5;
    }

    for (int w = 0; w < 8; ++w)
    {
        for (int x = 0; x < 8; ++x)
            DQ[w * 8 + x] = (int)(d[w][x]);
    }
    /*Quantification*/
    int *QDCT = this->DctQuantification(DQ, this->matrix);

    for (i = 0; i < 8; i++) {
        for (j = 0; j < 8; j++) {
            *(coeff + zigzag[i][j]) = (int)(QDCT[i * 8 + j]);
        }
    }
    delete QDCT;
    return 0;
}

int DctCompression::IDct(int *coeff,int *block)
{
    int 	        j1, i, j;
    double 	b[8], b1[8], d[8][8];
    double 	f0=.7071068;
    double  f1=.4903926;
    double  f2=.4619398;
    double  f3=.4157348;
    double	f4=.3535534;
    double 	f5=.2777851;
    double  f6=.1913417;
    double  f7=.0975452;
    double 	e, f, g, h;

    for (i = 0; i < 8; i++)
    {
        for (j = 0; j < 8; j++)
            b[j] = *( coeff + zigzag[i][j]);
        coeff = this->DctDequantification(coeff, this->matrix);

        e = b[1] * f7 - b[7] * f1;
        h = b[7] * f7 + b[1] * f1;
        f = b[5] * f3 - b[3] * f5;
        g = b[3] * f3 + b[5] * f5;

        b1[0] = (b[0] + b[4]) * f4;
        b1[1] = (b[0] - b[4]) * f4;
        b1[2] = b[2] * f6 - b[6] * f2;
        b1[3] = b[6] * f6 + b[2] * f2;
        b[4] = e + f;
        b1[5] = e - f;
        b1[6] = h - g;
        b[7] = h + g;

        b[5] = (b1[6] - b1[5]) * f0;
        b[6] = (b1[6] + b1[5]) * f0;
        b[0] = b1[0] + b1[3];
        b[1] = b1[1] + b1[2];
        b[2] = b1[1] - b1[2];
        b[3] = b1[0] - b1[3];

        for (j = 0; j < 4; j++)
        {
            j1 = 7 - j;
            d[i][j] = b[j] + b[j1];
            d[i][j1] = b[j] - b[j1];
        }
    }


    for (i = 0; i < 8; i++) {
        for (j = 0; j < 8; j++) {
            b[j] = d[j][i];
        }
        e = b[1] * f7 - b[7] * f1;
        h = b[7] * f7 + b[1] * f1;
        f = b[5] * f3 - b[3] * f5;
        g = b[3] * f3 + b[5] * f5;

        b1[0] = (b[0] + b[4]) * f4;
        b1[1] = (b[0] - b[4]) * f4;
        b1[2] = b[2] * f6 - b[6] * f2;
        b1[3] = b[6] * f6 + b[2] * f2;
        b[4] = e + f;
        b1[5] = e - f;
        b1[6] = h - g;
        b[7] = h + g;

        b[5] = (b1[6] - b1[5]) * f0;
        b[6] = (b1[6] + b1[5]) * f0;
        b[0] = b1[0] + b1[3];
        b[1] = b1[1] + b1[2];
        b[2] = b1[1] - b1[2];
        b[3] = b1[0] - b1[3];

        for (j = 0; j < 4; j++) {
            j1 = 7 - j;
            d[j][i] = b[j] + b[j1];
            d[j1][i] = b[j] - b[j1];
        }
    }
    coeff = (int*)d;
    for (i = 0; i < 8; i++) {
        for (j = 0; j < 8; j++) {
            *(block + i * 8 + j) = (int)(d[i][j]);
        }
    }
    return 0;
}

